package com.threeti.jiayusms.framework.dao;

import static org.hibernate.criterion.Projections.rowCount;
import static org.hibernate.criterion.Restrictions.eq;
import static org.springframework.util.CollectionUtils.isEmpty;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projection;
import org.hibernate.impl.CriteriaImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.threeti.jiayusms.framework.domain.DomainEntity;
import com.threeti.jiayusms.framework.util.PagedListHandler;

/**
 * DAO抽象默认实现
 *
 * @author Jay Meng
 */
public abstract class AbstractDao<T extends DomainEntity> extends HibernateDaoSupport implements GenericPagedDao<T> {
  private static final Logger logger = LoggerFactory.getLogger(AbstractDao.class);

  public AbstractDao(SessionFactory sessionFactory) {
    super.setSessionFactory(sessionFactory);
  }

  protected abstract DetachedCriteria buildDefaultCriteria();

  @Override
  public List<T> findAll() {
    DetachedCriteria criteria = buildDefaultCriteria();

    List<T> entites = getHibernateTemplate().findByCriteria(criteria);

    if (!isEmpty(entites)) return entites;

    return Collections.emptyList();
  }

  @SuppressWarnings("unchecked")
  @Override
  public PagedListHandler<T> findAllByPage(int currentPageIndex, int startingIndex) {
    DetachedCriteria criteria = buildDefaultCriteria();

    long totalSize = getTotalRowCountByCriteria(criteria);

    List<T> pagedList = getHibernateTemplate().findByCriteria(criteria, startingIndex, PagedListHandler.DEFAULT_PAGE_SIZE);

    if (!isEmpty(pagedList)) return new PagedListHandler(pagedList, totalSize, currentPageIndex, startingIndex);

    return PagedListHandler.EMPTY_LIST;
  }

  @SuppressWarnings("unchecked")
  @Override
  public PagedListHandler<T> findByPage(DetachedCriteria criteria, int currentPageIndex, int startingIndex) {

    long totalSize = getTotalRowCountByCriteria(criteria);

    List<T> pagedList = getHibernateTemplate().findByCriteria(criteria, startingIndex, PagedListHandler.DEFAULT_PAGE_SIZE);

    if (!isEmpty(pagedList)) return new PagedListHandler(pagedList, totalSize, currentPageIndex, startingIndex);

    return PagedListHandler.EMPTY_LIST;
  }

  @SuppressWarnings("unchecked")
  @Override
  public PagedListHandler<T> findByPageWithPassedInTotalRowCount(DetachedCriteria criteria, int currentPageIndex, int startingIndex, long totalSize) {

    List<T> pagedList = getHibernateTemplate().findByCriteria(criteria, startingIndex, PagedListHandler.DEFAULT_PAGE_SIZE);

    if (!isEmpty(pagedList)) return new PagedListHandler(pagedList, totalSize, currentPageIndex, startingIndex);

    return PagedListHandler.EMPTY_LIST;
  }
  
  
  public List<T> findAllBy(DetachedCriteria criteria) {
    List<T> pagedList = getHibernateTemplate().findByCriteria(criteria);

    if (!isEmpty(pagedList)) return pagedList;

    return pagedList;
  }

  @Override
  public T loadBy(Long id) {
    return loadByPKorCK(id, buildDefaultCriteria(), false);
  }

  @Override
  public T loadBy(Long id, boolean isLoadFromCache) {
    return loadByPKorCK(id, buildDefaultCriteria(), isLoadFromCache);
  }

  @Override
  public void remove(T domainEntity) {
    getHibernateTemplate().delete(domainEntity);
  }

  @Override
  public void removeBy(Long id) {
    getHibernateTemplate().delete(loadBy(id));
  }

  @Override
  public T store(T domainEntity) throws EntityAlreadyExistException, EntitySaveOrUpdateException {
    try {
      return this.getHibernateTemplate().merge(domainEntity);

    } catch (DataIntegrityViolationException e) {//TODO cant catch?refactor to throw advice?change tx policy?
      logger.error(e.getMessage(), e);
      throw new EntityAlreadyExistException(domainEntity.getClass(), e);

    } catch (DataAccessException e) {
      logger.error(e.getMessage(), e);
      throw new EntitySaveOrUpdateException(domainEntity.getClass(), e);

    } catch (Exception e) {
      logger.error(e.getMessage(), e);
      throw new EntitySaveOrUpdateException(domainEntity.getClass(), e);
    }
  }

  public final void forceInitialize(Object poOrCollectionProxy) {
    this.getHibernateTemplate().initialize(poOrCollectionProxy);
  }

  T loadByPKorCK(Serializable pkOrCk, DetachedCriteria criteria, boolean isLoadFromCache) {
    if (isLoadFromCache) {
      criteria.getExecutableCriteria(getSession()).setCacheable(true);
    }
    criteria.add(eq("id", pkOrCk));

    List<T> entites = this.getHibernateTemplate().findByCriteria(criteria);
    if(!isEmpty(entites)) return entites.get(0);
    return null;
  }

  @SuppressWarnings("unchecked")
  public long getTotalRowCountByCriteria(final DetachedCriteria detachedCriteria) {

    Long count = (Long) getHibernateTemplate().execute(
      new HibernateCallback() {
        public Object doInHibernate(Session session) throws HibernateException {
          Criteria criteria = detachedCriteria.getExecutableCriteria(session);
          Projection savedProjection = ((CriteriaImpl)criteria).getProjection();

          Long totalRowCount = (Long)criteria.setProjection(rowCount()).uniqueResult();

          criteria.setProjection(savedProjection);
          if (savedProjection == null) {
            criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
          }

          return totalRowCount;
        }
      });

    return count == null ? 0 : count;
  }
  
}
